#pragma once

#include "Core.h"
#include "Vertex.h"

namespace Gut
{
	struct Mesh
	{
		unsigned int VAO = 0;
		void Bind();
		virtual void Draw() const = 0;
		void Delete()
		{
			glDeleteVertexArrays(1, &VAO);
		}
	};

	struct MeshNoIndiceDesc
	{
		void *vertexheader;
		unsigned int vertexStride;
		size_t vertexNum;

		const VertexLayout *layout;
		size_t numLayout;

		int faceType = GL_TRIANGLES;
		int usage = GL_STATIC_DRAW;

		template <typename V>
		bool CreateFrom(const std::vector<V> &vertices, const VertexLayout *layout, int numLayout,
						int faceType = GL_TRIANGLES, int usage = GL_STATIC_DRAW)
		{
			this->vertexheader = &vertices[0];
			this->vertexStride = sizeof(V);
			this->vertexNum = vertices.size();

			this->layout = layout;
			this->numLayout = numLayout;

			this->faceType = faceType;
			this->usage = usage;

			return true;
		}

		template <typename V>
		bool CreateFrom(const std::vector<V> &vertices,
						int faceType = GL_TRIANGLES, int usage = GL_STATIC_DRAW)
		{
			this->vertexheader = (void *)&vertices[0];
			this->vertexStride = sizeof(V);
			this->vertexNum = vertices.size();

			const auto &layout = V::inputLayout;
			this->layout = &layout[0];
			this->numLayout = layout.size();

			this->faceType = faceType;
			this->usage = usage;

			return true;
		}
	};

	struct MeshNoIndice : public Mesh
	{
		// vertex
		size_t vertexNum;
		unsigned int faceType = GL_TRIANGLES;

		bool CreateFromDesc(
			const MeshNoIndiceDesc &desc);
		template <typename VertexType>
		bool CreateFrom(
			const std::vector<VertexType> &vertices,
			int faceType = GL_TRIANGLES,
			int usage = GL_STATIC_DRAW)
		{
			return CreateFrom<VertexType>(vertices, VertexType::inputLayout, faceType, usage);
		}
		template <typename VertexType>
		bool CreateFrom(
			const std::vector<VertexType> &vertices,
			const std::vector<VertexLayout> &inputLayout,
			int faceType = GL_TRIANGLES,
			int usage = GL_STATIC_DRAW)
		{

			MeshNoIndiceDesc desc;

			desc.CreateFrom<VertexType>(vertices, faceType, usage);
			ThrowIfFailed(CreateFromDesc(desc));
			this->faceType = faceType;

			return true;
		}
		void Draw() const
		{
			this->DrawArrays();
		}
		void DrawArrays() const;
	};

	struct MeshIndiceDesc
	{
		// vertex
		void *vertexheader;
		unsigned int vertexStride;
		size_t vertexNum;

		// indices
		void *indiceHeader;
		unsigned int indiceStride;
		size_t indiceNum;

		// layout
		const VertexLayout *layout;
		size_t numLayout;

		unsigned int face = GL_TRIANGLES;
		unsigned int usage = GL_STATIC_DRAW;

		template <typename V, typename I>
		inline bool CreateFrom(
			const VertexLayout *layout,
			const unsigned int numLayout,
			const unsigned int faceType = GL_TRIANGLES,
			const unsigned int usage = GL_STATIC_DRAW)
		{
			this->vertexheader = NULL;
			this->vertexNum = 0;
			this->vertexStride = sizeof(V);

			this->indiceHeader = NULL;
			this->indiceStride = sizeof(I);
			this->indiceNum = 0;

			this->layout = &layout[0];
			this->numLayout = numLayout;
			this->face = faceType;
			this->usage = usage;

			return true;
		}

		template <typename V, typename I>
		inline bool CreateFrom(
			const std::vector<V> &vertices,
			const std::vector<I> &indices,
			const VertexLayout *layout,
			const unsigned int numLayout,
			const unsigned int faceType = GL_TRIANGLES,
			const unsigned int usage = GL_STATIC_DRAW)
		{
			this->vertexheader = (void *)&vertices[0];
			this->vertexNum = vertices.size();
			this->vertexStride = sizeof(V);

			this->indiceHeader = (void *)&indices[0];
			this->indiceStride = sizeof(I);
			this->indiceNum = indices.size();

			this->layout = layout;
			this->numLayout = numLayout;
			this->face = faceType;
			this->usage = usage;

			return true;
		}
	};

	struct MeshIndice : public Mesh
	{
		unsigned int EBO, VBO;
		unsigned int numIndices;
		unsigned int startIndex = 0;
		unsigned int faceType = GL_TRIANGLES;
		unsigned int indiceType = GL_UNSIGNED_INT;
		unsigned int indiceStride = 4;

		bool CreateFromDesc(const MeshIndiceDesc &desc);

		template <typename V, typename I>
		void BufferSubData(const std::vector<V> &vs, std::vector<I> &iS)
		{
			BufferSubData((void *)&vs[0], sizeof(V) * vs.size(), (void *)&iS[0], sizeof(I) * iS.size());
		};

		void BufferSubData(void *vH, size_t vS, void *iH, size_t iS)
		{
			glBindVertexArray(this->VAO);
			glBindBuffer(GL_ARRAY_BUFFER, VBO);
			glBufferSubData(GL_ARRAY_BUFFER, 0, vS, vH);

			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
			glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, iS, iH);

			glBindVertexArray(0);
		}

		template <typename V, typename I>
		bool CreateVertexIndiceInputLayout(
			const std::vector<V> &vertices,
			const std::vector<I> &indices,
			const VertexLayout *layout,
			const unsigned int numLayout,
			const unsigned int faceType = GL_TRIANGLES,
			const unsigned int usage = GL_STATIC_DRAW)
		{
			MeshIndiceDesc desc;
			desc.CreateFrom<V, I>(vertices, indices, layout, numLayout, faceType, usage);
			return CreateFromDesc(desc);
		}

		template <typename V, typename I>
		bool CreateVertexIndiceInputLayout(
			const std::vector<V> &vertices,
			const std::vector<I> &indices,
			const std::vector<VertexLayout> &layout,
			const unsigned int faceType = GL_TRIANGLES,
			const unsigned int usage = GL_STATIC_DRAW)
		{
			return CreateVertexIndiceInputLayout(vertices, indices, &layout[0], layout.size(), faceType, usage);
		}

		template <typename V, typename I>
		bool CreateVertexIndiceInputLayout(
			const std::vector<V> &vertices,
			const std::vector<I> &indices,
			const unsigned int faceType = GL_TRIANGLES,
			const unsigned int usage = GL_STATIC_DRAW)
		{
			return CreateVertexIndiceInputLayout(vertices, indices, &V::inputLayout[0], V::inputLayout.size(), faceType, usage);
		}

		template <typename V, typename I>
		bool CreateVertexIndiceInputLayout(
			int numV, int numI,
			const unsigned int faceType = GL_TRIANGLES,
			const unsigned int usage = GL_STATIC_DRAW)
		{
			std::vector<V> vertices;
			std::vector<I> indices;
			vertices.resize(numV);
			indices.resize(numI);
			return CreateVertexIndiceInputLayout(vertices, indices, &V::inputLayout[0], V::inputLayout.size(), faceType, usage);
		}

		void Draw() const;
		void DrawInstanced(unsigned int count) const;
	};

};